/*
 *  CounterPtr.h
 *  utility
 *
 *  Created by serika on 10-10-6.
 *  Copyright 2010 serika. All rights reserved.
 *
 */

#ifndef ut_CounterPtr_h
#define ut_CounterPtr_h

#include "Exception.h"
#include "ExceptionCode.h"
#include "String.h"
#include <map>

namespace serika
{
	namespace ut
	{
		template <class Ptr>
		class CounterPtr
		{
		public:
			CounterPtr(Ptr* ptr);
			~CounterPtr();
			
			CounterPtr(const CounterPtr& lhs);
			CounterPtr& operator = (const CounterPtr& lhs);
			
		public:
			Ptr* operator -> ();
			Ptr& operator * ();
			
			void Delete();
			
		private:
			Ptr* mPtr;
				
			static typename std::map<Ptr*, int> sPtrMapping;
			
			enum { INVALID_INDEX = -1 };
			
			void Bind(Ptr* ptr);
			void Unbind();
		};
		
		template <class Ptr>
		std::map<Ptr*, int> CounterPtr<Ptr>::sPtrMapping;
		
		template <class Ptr>
		CounterPtr<Ptr>::CounterPtr(Ptr* ptr)
		{
			this->Bind(ptr);
		}
		
		template <class Ptr>
		CounterPtr<Ptr>::~CounterPtr()
		{
			this->Unbind();
		}
		
		template <class Ptr>
		CounterPtr<Ptr>::CounterPtr(const CounterPtr& lhs)
		{
			this->Bind(lhs.mPtr);
		}
		
		template <class Ptr>
		CounterPtr<Ptr>& CounterPtr<Ptr>::operator = (const CounterPtr& lhs)
		{
			if (this == &lhs)
			{
				return *this;
			}
			
			this->Unbind();
			this->Bind(lhs.mPtr);
		}
		
		template <class Ptr>
		void CounterPtr<Ptr>::Delete()
		{
			sPtrMapping[mPtr] = 0;
			delete mPtr;
		}
		
		template <class Ptr>
		Ptr* CounterPtr<Ptr>::operator -> ()
		{
			if (0 == sPtrMapping[mPtr])
			{
				throw Exception(ExceptionCode::CounterPtrDeleted,
								_T("counter ptr has been deleted"));
			}
			
			return mPtr;
		}
		
		template <class Ptr>
		Ptr& CounterPtr<Ptr>::operator * ()
		{
			if (0 == sPtrMapping[mPtr])
			{
				throw Exception(ExceptionCode::CounterPtrDeleted,
								_T("counter ptr has been deleted"));
			}
			
			return *mPtr;
		}
		
		template <class Ptr>
		void CounterPtr<Ptr>::Bind(Ptr* ptr)
		{
			mPtr = ptr;
			int& count = sPtrMapping[ptr];
			++count;
		}
		
		template <class Ptr>
		void CounterPtr<Ptr>::Unbind()
		{
			int& count = sPtrMapping[mPtr];
			if (0 == count)
			{
				return;
			}
			
			--count;
			if (0 == count)
			{
				delete mPtr;
			}
		}
	}
}
#endif
